package eval

import (
	"showen/num"
	"showen/obj"
	"strconv"
)

// NumberToString converts a number to its string representation
// (number->string number) - essential procedure
// (number->string number radix) - optional procedure
func NumberToString(args []obj.Object, env *obj.Environment) obj.Object {
	// Validate argument count
	if len(args) < 1 || len(args) > 2 {
		return &Error{Message: "number->string requires 1 or 2 arguments"}
	}

	// Validate that first argument is a number
	if args[0].Type() != obj.NUMBER_OBJ {
		return &Error{Message: "number->string first argument must be a number"}
	}

	// Get the number object
	numObj := args[0].(*obj.NumberObject).Value

	// Default radix is 10
	radix := 10

	// If radix is provided (second argument), validate it
	if len(args) == 2 {
		if args[1].Type() != obj.NUMBER_OBJ {
			return &Error{Message: "number->string second argument must be a number (radix)"}
		}

		radixObj := args[1].(*obj.NumberObject).Value
		if radixObj.Type() != num.INTEGER_TYPE {
			return &Error{Message: "number->string second argument must be an integer (radix)"}
		}

		// num.Integer is now a defined integer type; convert to int for radix
		radix = int(radixObj.(num.Integer))

		// Validate radix range (typically 2-36)
		if radix < 2 || radix > 36 {
			return &Error{Message: "number->string radix must be between 2 and 36"}
		}
	}

	// Convert number to string based on its type and radix
	switch numObj.Type() {
	case num.INTEGER_TYPE:
		// num.Integer is a defined int64-based type
		intVal := int64(numObj.(num.Integer))
		// For integer values, we can use Go's strconv.FormatInt
		if radix == 10 {
			return &obj.StringObject{Value: strconv.FormatInt(intVal, 10)}
		}
		// For other radices
		return &obj.StringObject{Value: strconv.FormatInt(intVal, radix)}

	case num.RATIONAL_TYPE:
		// For rational numbers, we only support radix 10
		if radix != 10 {
			return &Error{Message: "number->string only supports radix 10 for rational numbers"}
		}
		return &obj.StringObject{Value: numObj.String()}

	case num.REAL_TYPE:
		// For real numbers, we only support radix 10
		if radix != 10 {
			return &Error{Message: "number->string only supports radix 10 for real numbers"}
		}
		return &obj.StringObject{Value: numObj.String()}

	case num.COMPLEX_TYPE:
		// For complex numbers, we only support radix 10
		if radix != 10 {
			return &Error{Message: "number->string only supports radix 10 for complex numbers"}
		}
		return &obj.StringObject{Value: numObj.String()}
	}

	// Fallback - use the number's String() method
	return &obj.StringObject{Value: numObj.String()}
}

// StringToNumber converts a string to a number
// (string->number string) - essential procedure
// (string->number string radix) - optional procedure
func StringToNumber(args []obj.Object, env *obj.Environment) obj.Object {
	// Validate argument count
	if len(args) < 1 || len(args) > 2 {
		return &Error{Message: "string->number requires 1 or 2 arguments"}
	}

	// Validate that first argument is a string
	if args[0].Type() != obj.STRING_OBJ {
		return &Error{Message: "string->number first argument must be a string"}
	}

	// Get the string value
	str := args[0].(*obj.StringObject).Value

	// Default radix is 10
	radix := 10

	// If radix is provided (second argument), validate it
	if len(args) == 2 {
		if args[1].Type() != obj.NUMBER_OBJ {
			return &Error{Message: "string->number second argument must be a number (radix)"}
		}

		radixObj := args[1].(*obj.NumberObject).Value
		if radixObj.Type() != num.INTEGER_TYPE {
			return &Error{Message: "string->number second argument must be an integer (radix)"}
		}

		// convert num.Integer to int for radix
		radix = int(radixObj.(num.Integer))

		// Validate radix range (typically 2-36)
		if radix < 2 || radix > 36 {
			return &Error{Message: "string->number radix must be between 2 and 36"}
		}
	}

	// Try to parse the string as a number based on radix
	// For integers with specified radix
	if i, err := strconv.ParseInt(str, radix, 64); err == nil {
		return &obj.NumberObject{Value: num.Integer(i)}
	}

	// For radix 10, also try float parsing
	if radix == 10 {
		// Try to parse as a float
		if f, err := strconv.ParseFloat(str, 64); err == nil {
			// Check if it's actually an integer
			if f == float64(int64(f)) {
				return &obj.NumberObject{Value: num.Integer(int64(f))}
			}
			return &obj.NumberObject{Value: num.Real(f)}
		}
	}

	// If we can't parse it as a number, return #f (false)
	return &obj.BooleanObject{Value: false}
}
